package com.hfb.io.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * java SE 版 NIO SelectorServerSocket
 *
 *  思路：
 *      事件回调——事件驱动——观察者模式
 *  实现：
 *      多路复用器（底层是epoll），有个事件发生列表，
 *      只要用有限资源，处理这些已经发生的事件即可
 *  问题（缺点）分析：
 *      1、事件串行化处理，效率低。（如C10K问题中，有9K左右的时间发生了，需要处理，那串行化处理效率低下）
 *      2、连接超时。（如C10k问题中，还有更多客户端需要连接并处理事件，那么，此时服务器在崩溃边缘。）
 *      3、线程池版本的瓶颈就在线程池！
 *
 */
public class MyNIOSelectorServer_02 {

    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(9000));
        serverSocket.configureBlocking(false);// 设置为非阻塞
        // 打开Selector（多路复用器）， 处理Channel ，即创建 epoll
        Selector selector = Selector.open();
        // 把 ServerSocketChannel注册到Selector，并Selector对客户端 accept连接操作感兴趣
        SelectionKey selectionKey = serverSocket.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务启动成功");

        while (true) {
            // 阻塞等待需要处理的事情发生
            int eventNO = selector.select();
            System.out.printf("有%s个事件发生,请立即处理！", eventNO);

            // 遍历seleckorKey对事件进行处理
            Iterator<SelectionKey> iterator = selector.keys().iterator();
            iterator.forEachRemaining(sk->{
                // 如果是OP_ACCEPT事件，则进行连接的获取和时间的注册
                if(sk.isAcceptable()) {
                    ServerSocketChannel serversc = (ServerSocketChannel) sk.channel();
                    try {
                        SocketChannel socketChannel = serversc.accept();
                        socketChannel.configureBlocking(false);// 设置为非阻塞
                        // 写事件，向客户端写数据
                        SelectionKey selKey_w = socketChannel.register(selector, SelectionKey.OP_WRITE);

                        // 读事件，从客户端读数据
                        SelectionKey selKey_r = socketChannel.register(selector, SelectionKey.OP_READ);

                        System.out.println(socketChannel.getRemoteAddress()+"连接成功。。。");

                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } else if(sk.isReadable()) {
                    // 如果是读事件，则进行读取和打印
                    ServerSocketChannel serversc = (ServerSocketChannel) sk.channel();
                    try {
                        SocketChannel sc = serversc.accept();
                        ByteBuffer buffer = ByteBuffer.allocate(128);
                        int len = sc.read(buffer);

                        if (len > 0) {  // 有数据 打印
                            System.out.println("接收到客户端【"+
                                sc.getRemoteAddress()+"】消息："+
                                new String(buffer.array()));
                        } else if(len == -1){ // 客户端断开，移除 socket 列表
                            System.out.println("客户端【"+sc.getRemoteAddress()+"】断开连接!");
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                // 从事件集合里移除，避免下次重复处理
                iterator.remove();
            });
        }
    }
}
